/*****************************************************************************\

  Microsoft Research Asia
  Copyright (c) 2004 Microsoft Corporation
  
	Module Name:
	
	  CmdLine.h:     definition and implementation of command line parsing

Notes:

History:

  Created on Apr.40.2004
	Lei Zhang, leizhang@microsoft.com
  Modified on June,3,2004
    Lei Zhang, leizhang@microsoft.com
		Added #include <stdarg.h>
		Change to Unicode version
    
\*****************************************************************************/
#ifndef _PARSER_H_
#define _PARSER_H_

#pragma once
#pragma warning(push)
#pragma warning(disable: 4996)
#include <stdarg.h>
#include <vector>
#include <string>

#ifndef tstring
#ifdef _UNICODE
#define tstring wstring
#else
#define tstring string
#endif
#endif

/**********************************************************
// class CCmdArg: basic cmd arg class
**********************************************************/
class CCmdArg
{
public:
	enum  CmdArgSyntax 
	{
		isOPT       = 0x00,  // argument is optional
		isREQ       = 0x01,  // argument is required
		isVALOPT    = 0x02,  // argument value is optional
		isVALREQ    = 0x04,  // argument value is required
		isLIST      = 0x20,  // argument is a list
		isPOS       = 0x40,  // argument is positional
		isHIDDEN    = 0x80,  // argument is not to be printed in usage
		isVALTAKEN  = (isVALREQ | isVALOPT),    // argument takes a value
		isOPTVALOPT = (isOPT | isVALOPT),
		isOPTVALREQ = (isOPT | isVALREQ),
		isPOSVALOPT = (isPOS | isVALOPT),
		isPOSVALREQ = (isPOS | isVALREQ),
	};

	CCmdArg(char cOpchar, char *szKeyword, char *szValue, char * szDescription, unsigned int syntax_flags)
	{
		m_cOpchar = cOpchar;
		m_szKeyword = szKeyword;
		m_szValue = szValue;
		m_szDescription = szDescription;
		m_dwSyntaxFlags = syntax_flags;
		m_bArgAvailable = false;
	}
	char GecharName() { return m_cOpchar; }
	const char *GetKeywordName() { return m_szKeyword; }
	const char *GetValueName() { return m_szValue; }
	const char *GetDescription() { return m_szDescription; }
	unsigned int GetFlags() { return m_dwSyntaxFlags; }
	virtual int SetValue(char *argv[]) = 0;
	bool IsAvailable() { return m_bArgAvailable; }
	void SetAvailable() { m_bArgAvailable = true; }
	virtual int ValueNeeded() { return 1; }
private:
	char	m_cOpchar;
	char	*m_szKeyword;
	char	*m_szValue;
	char	*m_szDescription;
	unsigned int m_dwSyntaxFlags;
	bool	m_bArgAvailable;
};

class CCmdArgInt : public CCmdArg
{
public:
	CCmdArgInt(char cOpchar, 
		       char *szKeyword, 
			   char *szValue, 
			   int iDefault,
			   char* szDescription, 
			   unsigned int syntax_flags = CCmdArg::isOPTVALREQ)
		: CCmdArg(cOpchar, szKeyword, szValue, szDescription, syntax_flags), m_iValue(iDefault) {}
	operator int(void)  const { return  m_iValue; }
	virtual int SetValue(char *argv[])
	{
		m_iValue = atoi(*argv++);
		return 0;
	}
private:
	int		m_iValue;
};

class CCmdArgIntList : public CCmdArg
{
public:
	CCmdArgIntList(char cOpchar, 
		       char *szKeyword, 
			   char *szValue, 
			   int iDefault,
			   int iIntNumber,
			   char* szDescription, 
			   unsigned int syntax_flags = CCmdArg::isOPTVALREQ)
		: CCmdArg(cOpchar, szKeyword, szValue, szDescription, syntax_flags)
	{
		m_arrValue.resize(iIntNumber);
		for (int i = 0; i < iIntNumber; i++)
			m_arrValue[i] = iDefault;
	}
	int operator[] (int i) { return m_arrValue[i]; }
	virtual int SetValue(char *argv[])
	{
		for (unsigned int i = 0; i < m_arrValue.size(); i++)
		{
			m_arrValue[i] = atoi(*argv++);
		}
		return 0;
	}
	virtual int ValueNeeded() { return (int)m_arrValue.size(); }
private:
	std::vector<int>	m_arrValue;
};

class CCmdArgBool: public CCmdArg
{
public:
	CCmdArgBool(char cOpchar,
				char *szKeyword,
				bool bDefault,
				char* szDescription, 
				unsigned int syntax_flags = CCmdArg::isOPT)
		: CCmdArg(cOpchar, szKeyword, NULL, szDescription, syntax_flags), m_bValue(bDefault) {}
	operator bool(void)  const { return  m_bValue; }
	virtual int SetValue(char *argv[])
	{
		m_bValue = !m_bValue;
		return 0;
	}
	virtual int ValueNeeded() { return 0; }
private:
	bool	m_bValue;
};

class CCmdArgFloat : public CCmdArg
{
public:
	CCmdArgFloat(char cOpchar, 
		         char *szKeyword, 
				 char *szValue, 
				 double lfDefault,
				 char* szDescription, 
				 unsigned int syntax_flags = CCmdArg::isOPTVALREQ)
		: CCmdArg(cOpchar, szKeyword, szValue, szDescription, syntax_flags), m_lfValue(lfDefault) {}
	operator double(void)  const { return  m_lfValue; }
	operator float(void)  const { return  (float)m_lfValue; }
	virtual int SetValue(char *argv[])
	{
		m_lfValue = atof(*argv++);
		return 0;
	}
private:
	double	m_lfValue;
};

class CCmdArgFloatList : public CCmdArg
{
public:
	CCmdArgFloatList(char cOpchar, 
		       char *szKeyword, 
			   char *szValue, 
			   double lfDefault,
			   int iFloatNumber,
			   char* szDescription, 
			   unsigned int syntax_flags = CCmdArg::isOPTVALREQ)
		: CCmdArg(cOpchar, szKeyword, szValue, szDescription, syntax_flags)
	{
		m_arrValue.resize(iFloatNumber);
		for (int i = 0; i < iFloatNumber; i++)
			m_arrValue[i] = lfDefault;
	}
	double operator[] (int i) { return m_arrValue[i]; }
	virtual int SetValue(char *argv[])
	{
		for (unsigned int i = 0; i < m_arrValue.size(); i++)
		{
			m_arrValue[i] = atof(*argv++);
		}
		return 0;
	}
	virtual int ValueNeeded() { return (int)m_arrValue.size(); }
private:
	std::vector<double>	m_arrValue;
};

class CCmdArgString : public CCmdArg
{
public:
	CCmdArgString(char cOpchar, 
		          char *szKeyword, 
				  char *szValue, 
				  char *szDefault,
				  char* szDescription, 
				  unsigned int syntax_flags = CCmdArg::isOPTVALREQ)
		: CCmdArg(cOpchar, szKeyword, szValue, szDescription, syntax_flags), m_str(szDefault) {}
	operator const char *(void)  const { return  m_str.c_str(); }
	virtual int SetValue(char *argv[])
	{
		m_str = *argv++;
		return 0;
	}
private:
	std::tstring m_str;
};

class CCmdArgStringList : public CCmdArg
{
public:
	CCmdArgStringList(char cOpchar, 
		          char *szKeyword, 
				  char *szValue, 
				  char *szDefault,
				  int iStringNumber,
				  char* szDescription, 
				  unsigned int syntax_flags = CCmdArg::isOPTVALREQ)
		: CCmdArg(cOpchar, szKeyword, szValue, szDescription, syntax_flags)
	{
		m_arrStr.resize(iStringNumber);
		for (int i = 0; i < iStringNumber; i++)
			m_arrStr[i] = szDefault;
	}
	const char * operator[] (int i) { return m_arrStr[i].c_str(); }
	virtual int SetValue(char *argv[])
	{
		for (unsigned int i = 0; i < m_arrStr.size(); i++)
		{
			m_arrStr[i] = *argv++;
		}
		return 0;
	}
	virtual int ValueNeeded() { return (int)m_arrStr.size(); }
private:
#ifdef _UNICODE
	std::vector<std::wstring> m_arrStr;
#else
	std::vector<std::string> m_arrStr;
#endif
};

/**********************************************************
class CTaskItem: include a set of params
***********************************************************/
class CTaskItem
{
	std::vector<CCmdArg*> m_arrArgList;
public:
	char*	m_szDescription;	//e.g. "detect folder: detect the face in the whole image folder
	char*	m_szTaskName;		//e.g. "Folder"
	int		(*m_MethodFunction)();
	CTaskItem()
	{
		m_szDescription = m_szTaskName = NULL;
	}
	CTaskItem(char* szDescription, char* szTaskName, int (*pFun)(), ...);
	int Size() { return (int)m_arrArgList.size(); }
	CCmdArg &GetAt(int i) { return *m_arrArgList[i]; }
	CCmdArg &operator[] (int i) { return *m_arrArgList[i]; }
};

inline CTaskItem::CTaskItem(char* szDescription, char* szTaskName, int (*pFun)(), ...)
{
	m_szDescription = szDescription;
	m_szTaskName =  szTaskName;
	m_MethodFunction = pFun;
	
	va_list marker;
	va_start(marker, pFun);     /* Initialize variable arguments. */
	for (CCmdArg* pParam = (CCmdArg*) va_arg(marker, void*);
		 pParam != NULL;
		 pParam = (CCmdArg*) va_arg(marker, void*))
	{
		m_arrArgList.push_back(pParam);
	}
	va_end( marker );              /* Reset variable arguments. */
}

/**********************************************************
class CParser: Parse the arguments of .exe file
***********************************************************/
class CParser
{
public:
	CParser() { m_iTaskIndex = -1;};
	~CParser() {};
	CParser(char* szAppName, ...);
	bool	Parse(int argc, char* argv[]);
	void	SetVersion(char* szVersion) {_tcscpy(m_szVersion, szVersion);}
	const char* GetExeFileName() const { return m_szExeFileName;	}
	int		Run();
protected:
	bool		ParseArgs(int argc, char* argv[]);
	bool		ParseTask(int argc, char* argv[]);
	std::vector<CTaskItem*>	m_arrTaskItems;
	int			m_iTaskIndex;
	void		DisplayTaskDetails();
	void		DisplayAllTask();
	char*		szAppName;
	char		m_szVersion[_MAX_PATH];
	char		m_szExeFileName[_MAX_PATH];
};

inline CParser::CParser(char* szProgramName, ...)
{
	szAppName = szProgramName;
	va_list marker;
	va_start(marker, szProgramName);     /* Initialize variable arguments. */
	for (CTaskItem* pItem = (CTaskItem*) va_arg(marker, void*);
		pItem != NULL;
		pItem = (CTaskItem*) va_arg(marker, void*))
	{
		m_arrTaskItems.push_back(pItem);
	}
	va_end( marker );					/* Reset variable arguments.      */
	return ;
}

inline int CParser::Run()
{
	if (m_iTaskIndex < 0)
	{
		return false;
	}
	int (*fun)();
	fun = m_arrTaskItems[m_iTaskIndex]->m_MethodFunction;
	return fun();
}

inline void CParser::DisplayAllTask()
{
	_tprintf(_T("Usage: %s [tasktype]\n"), szAppName);
	_tprintf(_T("\n"));
	_tprintf(_T("Task type:\n"));
	for (unsigned int i = 0; i < m_arrTaskItems.size(); i ++)
	{
		_tprintf(_T("        %-10s %s\n"), m_arrTaskItems[i]->m_szTaskName, m_arrTaskItems[i]->m_szDescription);
	}
	_tprintf(_T("\n"));
	_tprintf(_T("%s\n"), m_szVersion);
}

inline void CParser::DisplayTaskDetails()
{
	char buf[1024];
	//printf("Task: %s\n", m_arrTaskItems[m_iTaskIndex]->m_szTaskName);
	_tprintf(_T("%s %s"), szAppName, m_arrTaskItems[m_iTaskIndex]->m_szTaskName);
	for (int i = 0; i < m_arrTaskItems[m_iTaskIndex]->Size(); i++)
	{
		CCmdArg *pArg = &m_arrTaskItems[m_iTaskIndex]->GetAt(i);
		if (pArg->GecharName() == 0)
			_tprintf(_T(" [-%s"), pArg->GetKeywordName());
		else
			_tprintf(_T(" [-%c"), pArg->GecharName());
		if (pArg->GetValueName() == NULL)
			_tprintf(_T("]"));
		else
			_tprintf(_T(" %s]"), pArg->GetValueName());
	}
	_tprintf(_T("\n"));
	_tprintf(_T("\n"));
	_tprintf(_T("Options/Arguments:\n"));
//	printf("=============================================\n");
	for (int i = 0 ;i < m_arrTaskItems[m_iTaskIndex]->Size(); i++)
	{
		CCmdArg *pArg = &m_arrTaskItems[m_iTaskIndex]->GetAt(i);
		if (pArg->GetKeywordName() == 0)
			_stprintf(buf, _T("        -%c"), pArg->GecharName());
		else if (pArg->GecharName() == 0)
			_stprintf(buf, _T("        -%s"), pArg->GetKeywordName());
		else
			_stprintf(buf, _T("        -%c|%s"), pArg->GecharName(), pArg->GetKeywordName());

		if (pArg->GetValueName() != NULL)
		{
			_tcscat(buf, _T(" "));
			_tcscat(buf, pArg->GetValueName());
		}
		_tprintf(_T("%-31s %s\n"), buf, pArg->GetDescription());
	}
	//printf("---------------------------------------------\n");
	_tprintf(_T("\n"));
	_tprintf(_T("%s\n"), m_szVersion);
	return ;
}

inline bool CParser::ParseArgs(int argc, char* argv[])
{
	if (argc <= 2)
		return false;
	for( int i = 2; i < argc; i++)
    {
        if ( argv[i][0] != '-') 
		{
            printf("Switch '-' is expected before %s\n\n", argv[i]);
			return false;
		}
		CCmdArg *pArg;
		int iItemIndex;
		for (iItemIndex = 0; iItemIndex < m_arrTaskItems[m_iTaskIndex]->Size(); iItemIndex++)
		{
			pArg = &m_arrTaskItems[m_iTaskIndex]->GetAt(iItemIndex);
			if (argv[i][2] != 0 && pArg->GetKeywordName() != NULL)
			{
				if (_tcsncmp(argv[i] + 1, pArg->GetKeywordName(), _tcslen(argv[i]) - 1) == 0)
				{
					break;
				}
			}
			else if (argv[i][2] == 0 && argv[i][1] == pArg->GecharName())
			{
				break;
			}
		}
		if (iItemIndex < m_arrTaskItems[m_iTaskIndex]->Size())
		{
			if (i + pArg->ValueNeeded() >= argc)
			{
				char buf[50];
				if (pArg->GetKeywordName() == 0)
					_stprintf(buf, _T("-%c"), pArg->GecharName());
				else if (pArg->GecharName() == 0)
					_stprintf(buf, _T("-%s"), pArg->GetKeywordName());
				else
					_stprintf(buf, _T("-%c|%s"), pArg->GecharName(), pArg->GetKeywordName());
	
				_tprintf(_T("Insufficient values for option %s, which requirs %d values\n\n"), 
					buf, pArg->ValueNeeded());
				return false;
			}
			else
			{
				pArg->SetValue(argv + i + 1);
				i += pArg->ValueNeeded();
				pArg->SetAvailable();
			}
		}
		else
		{
			printf("Unknown option %s \n\n", argv[i-1]);
			return false;
		}
	}

	return true;
}

inline bool CParser::ParseTask(int argc, char* argv[])
{
	if (argc < 2)
	{
		return false;
	}
	for (unsigned int i = 0; i < m_arrTaskItems.size(); i ++)
	{
		if (_tcsicmp(argv[1], m_arrTaskItems[i]->m_szTaskName) == 0)
		{
			m_iTaskIndex = i;
			break;
		}
	}
	return true;
}

inline bool CParser::Parse(int argc, char* argv[])
{
	int iLen = (int)strlen(argv[0]);
	strcpy_s(m_szExeFileName,MAX_PATH, argv[0]);
	if (_stricmp(argv[0]+iLen-4, ".exe")!=0)
	{
		strcat_s(m_szExeFileName,MAX_PATH, ".exe");
	}
	if (ParseTask(argc, argv))
	{
		if (ParseArgs(argc, argv))
		{
			return true;
		}
		else
		{
			DisplayTaskDetails();
			return false;
		}
	}
	else 
	{
		DisplayAllTask();
		return false;
	}
	return true;
}
#pragma warning(pop)
#endif //_PARSER_H_